﻿
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using PS.Engine.DataTypes;
using PS.Stereotypes;
using System.Collections.Generic;
using System.Diagnostics;
using PS.Engine.Strategies;
using PS.DataTypes;
using System.Collections;

namespace PS.Engine.GameComponents
{
    public class Pedestrian : BaseGameComponent
    {
        #region Constants

        private const float AtDestinationLimit = 5f;
        // general movementspeed = 60
        private const float GeneralMovementSpeed = 60f;

        #endregion

        private float distanceToDestination = 999.9f;
        private World world;

        #region Constructors

        internal Pedestrian(Game game, float locationX, float locationY, Waypoint startWaypoint, Waypoint targetWaypoint, List<Stereotype> s, World world, PriorityQueue<int, WaitStrategy> strategies)
            : base(game, locationX, locationY)
        {
            this.AtGoal = false;
            this.TargetWaypoint = targetWaypoint;
            this.StartWaypoint = startWaypoint;
            this.Stereotypes = s;
            this.MinSpeed = 999.9f; // set high initial min speed
            this.world = world;
            base.color = Color.DarkBlue;
            Strategies = strategies;
        }

        #endregion

        #region Properties

        internal Vector2 Direction { get; set; }

        internal Waypoint TargetWaypoint { get; set; }

        internal Waypoint StartWaypoint { get; set; }

        internal WaypointList Waypoints { get; set; }

        internal float InitialDistanceToGoal { get; private set; }

        internal float ActualDistanceToGoal { get; private set; }

        internal double TimeToGoal { get; private set; }

        internal float MinSpeed { get; private set; }

        internal float MaxSpeed { get; private set; }

        internal bool AtGoal { get; set; }

        private float DistanceToDestination
        {
            get
            {
                return distanceToDestination;
            }
        }

        internal bool AtDestination
        {
            get
            {
                return DistanceToDestination < AtDestinationLimit;
            }
        }

        internal List<Stereotype> Stereotypes { get; set; }

        internal PriorityQueue<int, WaitStrategy> Strategies { get; set; }

        #endregion

        #region Lifecycle overrides

        public override void Update(GameTime gameTime)
        {
            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (Waypoints != null && Waypoints.Count > 0)
            {
                distanceToDestination = Vector2.Distance(Location, Waypoints.Peek().Location);

                if (AtDestination)
                {
                    ActualDistanceToGoal += distanceToDestination; // don't forget the steps we get for free ;-)
                    Waypoints.Dequeue();
                }
                else
                {
                    // Check if a collision between pedestrians happens
                    Pedestrian collisionar = null;
                    bool wait = false;
                    if (world.PedestrianCollisionHappens(this, out collisionar))
                    {
                        // Handle collision detection with strategies
                        base.color = Color.Red;
                        WaitStrategy strategy = Strategies.Peek();
                        wait = strategy.Wait(this, collisionar);
                    }
                    else
                    {
                        base.color = Color.Black;
                    }

                    // Pedestrian has to wait until the more boor pedestrian walks out of collision range
                    if (!wait)
                    {
                        Vector2 direction = -(Location - Waypoints.Peek().Location);
                        direction.Normalize();
                        Direction = direction;
                        Vector2 step = (Direction * GetMovementSpeed() * elapsedTime);
                        ActualDistanceToGoal += step.Length();
                        Location = Location + step;   
                    }
                }
            }
            else // goal reached
            {
                // if just reached the goal, calculate some information about how was the travel
                if (!AtGoal)
                {
                    TimeToGoal = gameTime.TotalGameTime.TotalMilliseconds;
                    AtGoal = true;
                }
            }
        }

        /// <summary>
        /// Loads the specific content (e.g. texture) for the Pedestrian class.
        /// </summary>
        protected override void LoadContent()
        {
            texture = Game.Content.Load<Texture2D>("Pedestrian");
            base.LoadContent();
        }

        #endregion

        #region Public Methods

        internal void CalculateInitialDistance()
        {
            // get waypoints as array
            Waypoint[] waypointsArray = Waypoints.GetWaypointArray();

            // sum up all lenght
            for (int i = 0; i < waypointsArray.Length - 1; i++)
            {
                Vector2 section = waypointsArray[i + 1].Location - waypointsArray[i].Location;
                InitialDistanceToGoal += section.Length();
            }
        }

        internal double GetBoorFactor()
        {
            double boorFactor = 1;

            foreach (Stereotype s in Stereotypes)
            {
                boorFactor *= s.GetBoorFactor();
            }

            return boorFactor;
        }

        #endregion Public Methods

        #region private Methods

        private float GetMovementSpeed()
        {
            float movementSpeed = GeneralMovementSpeed;

            foreach (Stereotype s in Stereotypes)
            {
                movementSpeed *= s.GetSpeedFactor();
            }

            // set min and max speed
            if (movementSpeed < MinSpeed) MinSpeed = movementSpeed;
            if (movementSpeed > MaxSpeed) MaxSpeed = movementSpeed;

            return movementSpeed;
        }

        #endregion
    }
}
